import datetime

import bcrypt
import jwt
from sqlalchemy import select
from sqlalchemy.orm import Session

from common.exception.biz_exception import BizException
from common.response.biz_response_code import ERROR
from common.translate.biz_message import messages, load_messages
from model.dto.sys.login_token_dto import LoginTokenDto
from model.entity.sys.role import Role
from model.entity.sys.user import User
from model.entity.sys.user_roles import UserRoles
from model.request.sys.login_request import LoginRequest
from service.sys.user_service import UserService
from service_impl.sys.captcha_service_impl import CaptchaServiceImpl
from util.yaml_util import load_yaml_config

# 秘钥，用于加密和解密
SECRET_KEY = load_yaml_config('config/token.yaml', "token", "secret_key")
# 访问 token 过期时间，单位小时
ACCESS_EXPIRE = load_yaml_config('config/token.yaml', "token", "access_expire")
# 刷新 token 过期时间，单位小时
REFRESH_EXPIRE = load_yaml_config('config/token.yaml', "token", "access_expire")

captcha_service = CaptchaServiceImpl()


def generate_token(user_id, username, language, roles) -> LoginTokenDto:
    """
    生成访问令牌和刷新令牌
    :param user_id:
    :param username:
    :param language:
    :param roles:
    :return: 访问token和刷新token
    """
    role_ids = [role.id for role in roles]
    access_payload = {
        'user_id': user_id,
        'username': username,
        'language': language,
        'roles': role_ids,
        'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=ACCESS_EXPIRE)  # 令牌有效期
    }
    refresh_payload = {
        'user_id': user_id,
        'language': language,
        'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=REFRESH_EXPIRE)  # 刷新token有效期为30天
    }
    access_token = jwt.encode(access_payload, SECRET_KEY, algorithm='HS256')
    refresh_token = jwt.encode(refresh_payload, SECRET_KEY, algorithm='HS256')

    return LoginTokenDto(accessToken=access_token, refreshToken=refresh_token)


def generate_new_access_token(user_id, username, language, roles, refresh_token) -> LoginTokenDto:
    """
    生成新的访问令牌
    :param user_id: 用户ID
    :param username: 用户名
    :param language: 语言
    :param roles: 角色列表
    :param refresh_token: 原有的刷新令牌
    :return: 新的访问令牌和原有的刷新令牌
    """
    role_ids = [role.id for role in roles]
    access_payload = {
        'user_id': user_id,
        'username': username,
        'language': language,
        'roles': role_ids,
        'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=ACCESS_EXPIRE)  # 令牌有效期
    }
    new_access_token = jwt.encode(access_payload, SECRET_KEY, algorithm='HS256')

    return LoginTokenDto(accessToken=new_access_token, refreshToken=refresh_token)


def decode_token(token):
    """
    解码 token
    :param token:
    :return:
    """
    try:
        payload = jwt.decode(token, SECRET_KEY, algorithms=['HS256'])
        return payload
    except jwt.ExpiredSignatureError:
        raise BizException(ERROR, messages['TOKEN_EXPIRED'])
    except jwt.InvalidTokenError:
        raise BizException(ERROR, messages['INVALID_TOKEN'])


def login(db: Session, login_request: LoginRequest, user: User) -> LoginTokenDto:
    """
    登录
    :param db:
    :param login_request:
    :param user:
    :return:
    """
    user_id = user.id
    language = login_request.language
    is_password_valid = bcrypt.checkpw(login_request.password.encode('utf-8'), user.password.encode('utf-8'))
    if is_password_valid:
        # 查询用户的角色
        stmt = select(UserRoles).where(UserRoles.user_id == user_id)  # type: ignore
        user_roles = db.execute(stmt).scalars().all()
        role_ids = [user_role.role_id for user_role in user_roles]
        stmt = select(Role).where(Role.id.in_(role_ids), Role.enable)  # type: ignore
        roles = db.execute(stmt).scalars().all()
        if len(roles) == 0:
            raise BizException(ERROR, messages['NO_AVAILABLE_ROLE'])
        # 生成令牌
        return generate_token(user_id, user.username, language, roles)
    else:
        raise BizException(ERROR, messages['INCORRECT_PASSWORD'])


class UserServiceImpl(UserService):

    def refresh_token(self, db: Session, refresh_token: str) -> LoginTokenDto:
        """
        刷新访问令牌
        :param db: 数据库会话
        :param refresh_token: 刷新令牌
        :return: 新的访问令牌和原有的刷新令牌
        """
        try:
            refresh_payload = decode_token(refresh_token)
            user_id = refresh_payload['user_id']
            language = refresh_payload['language']

            # 获取用户信息和角色
            stmt = select(User).where(User.id == user_id, User.enable)  # type: ignore
            user = db.execute(stmt).scalar_one_or_none()

            if user is None:
                raise BizException(ERROR, messages['USER_NOT_EXIST'])

            # 查询用户的角色
            stmt = select(UserRoles).where(UserRoles.user_id == user_id)  # type: ignore
            user_roles = db.execute(stmt).scalars().all()
            role_ids = [user_role.role_id for user_role in user_roles]
            stmt = select(Role).where(Role.id.in_(role_ids))  # type: ignore
            roles = db.execute(stmt).scalars().all()

            # 生成新的访问token
            return generate_new_access_token(user_id, user.username, language, roles, refresh_token)
        except jwt.ExpiredSignatureError:
            raise BizException(ERROR, messages['REFRESH_TOKEN_EXPIRED'])
        except jwt.InvalidTokenError:
            raise BizException(ERROR, messages['INVALID_REFRESH_TOKEN'])

    def login(self, db: Session, login_request: LoginRequest) -> LoginTokenDto:
        """
        登录
        :param db:
        :param login_request:
        :return:
        """
        username = login_request.username
        captcha_key = login_request.captcha_key
        captcha = login_request.captcha
        # 根据登录语言读取对应语言的消息
        if login_request.language is not None:
            messages.update(load_messages(login_request.language))
        stmt = select(User).where(User.username == username, User.enable)  # type: ignore
        user = db.execute(stmt).scalar_one_or_none()
        # 账号不存在
        if user is None:
            raise BizException(ERROR, messages['USER_NOT_EXIST'])
        # 预览环境下可快速登录，不用验证码
        if login_request.is_quick:
            return login(db, login_request, user)
        # 需要验证码的情况
        if not captcha_service.verify(captcha_key, captcha):
            raise BizException(ERROR, messages['INCORRECT_VERIFICATION_CODE'])
        return login(db, login_request, user)
